﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using Adp.Pleiade.Fwk.Service.WCF;
using System.Xml;
using Adp.Pleiade.Fwk.Common;
using System.Collections;

namespace OutillageTests
{
    public static class GenerationClasseTU
    {
        private static StreamWriter sW;
        private static XmlDocument document = new XmlDocument();
        
        public static void GenererClassesTU(string path, Dictionary<Type, Dictionary<MethodInfo, bool>> uneCarto)
        {
            if (!Directory.Exists(path)) Directory.CreateDirectory(path);
            
            foreach (KeyValuePair<Type, Dictionary<MethodInfo, bool>> elementCarto in uneCarto)
            {
                // Exclusion des interfaces
                if (!elementCarto.Key.IsInterface)
                {
                    // Création du writer
                    sW = new StreamWriter(path + elementCarto.Key.Name + "Test.cs");

                    InsererEnteteTU(elementCarto);

                    // Initialisation du haut de corps de classe
                    InsererHautClasseTU(elementCarto, "C:/Generation TU/TUXML/");

                    foreach (KeyValuePair<MethodInfo, bool> methode in elementCarto.Value)
                    {
                        // Si une méthode n'est pas testable, elle n'est pas déclarée
                        if (methode.Value && ! methode.Key.IsPrivate)
                        {
                            string failureMessage = "";
                            bool nonTestable = false;

                            InitialiserMethodeTU(methode.Key, "C:/Generation TU/TUXML/", elementCarto.Key.Name);

                            // Traitement de la méthode de test
                            DeclarerCible(elementCarto, methode.Key);

                            // Gestion des paramètres
                            foreach (ParameterInfo parameter in methode.Key.GetParameters())
                            {
                                string decla = DonnerChaineDeDeclaration(parameter, false);
                                if (decla.Contains("Type non traité")) nonTestable = true;
                                sW.WriteLine("                  " + decla);
                            }

                            sW.WriteLine("");

                            if (nonTestable)
                                failureMessage = "                  Assert.Fail(\"Un ou plusieurs paramètres ne sont pas pris en charge.\");";
                            else
                            {
                                // Gestion du retour
                                if (typeof(void).IsAssignableFrom(Tools.GetNormalizedType(methode.Key.ReturnType)))
                                    failureMessage = "              Assert.Inconclusive(\"La méthode testée n'a pas de type de retour. Tester les états associés manuellement\");";
                                else
                                    InsererCorpsMethodeTest(methode);
                            }

                            sW.WriteLine("");

                            InsererFinDeMethode();

                            if (nonTestable || failureMessage.Length > 0) InsererAssertionTestKO(failureMessage);
                            else InsererAssertionTestOK();
                        }
                    }

                    // Fin de fichier
                    sW.WriteLine("   }");
                    sW.WriteLine("}");

                    // Fermeture du writer
                    sW.Flush();
                    sW.Close();
                }
            }
        }

        /// <summary>
        /// Insertion du bloc catch et de fin de méthode
        /// </summary>
        private static void InsererFinDeMethode()
        {
            sW.WriteLine("              }");
            sW.WriteLine("              catch (Exception e)");
            sW.WriteLine("              {");
            sW.WriteLine("                  Assert.Fail(\"Une erreur est survenue lors de l'exécution du test.\", e.InnerException, e.StackTrace);");
            sW.WriteLine("              }");
            sW.WriteLine("");
        }

        /// <summary>
        /// Insertion du bloc Assert.IsTrue(...)
        /// </summary>
        private static void InsererAssertionTestOK()
        {
            // Assertion par défaut
            sW.WriteLine("              // Test du retour de la méthode. Par défaut, tous les tests sont OK, sauf ceux rencontrant une erreur.");
            sW.WriteLine("              // Assert.AreEqual(expected, actual);");
            sW.WriteLine("              Assert.IsTrue(true);");
            sW.WriteLine("        }");
            sW.WriteLine("");
        }

        /// <summary>
        /// Insertion du bloc Assert.Fail()
        /// </summary>
        /// <param name="message"></param>
        private static void InsererAssertionTestKO(string message)
        {
            sW.WriteLine(message);
            sW.WriteLine("        }");
            sW.WriteLine("");
        }

        /// <summary>
        /// Insertion des déclarations de paramètres
        /// </summary>
        /// <param name="methode"></param>
        private static void InsererCorpsMethodeTest(KeyValuePair<MethodInfo, bool> methode)
        {
            string paramTrans;        
            
            sW.WriteLine("");
            sW.WriteLine("                  " + DonnerChaineDeDeclaration(methode.Key.ReturnParameter, true));

            // Appel de la méthode
            sW.WriteLine("                  // Appel de la cible");
            sW.Write("                  var actual = target." + methode.Key.Name + "(");

            // Construction du jeu de paramètres 
            int index = 0;
            foreach (ParameterInfo paramInfo in methode.Key.GetParameters())
            {
                paramTrans = "";
                if (paramInfo.ParameterType.IsByRef)
                {
                    if (paramInfo.IsOut) paramTrans = "out ";
                    else paramTrans = "ref ";
                }

                if (index > 0) sW.Write(", ");
                sW.Write(paramTrans + paramInfo.Name);
                index++;
            }
            sW.Write(");");
            sW.Write("");
            sW.WriteLine("");
        }

        /// <summary>
        /// Insertion de la déclaration de la cible
        /// </summary>
        /// <param name="elementCarto"></param>
        /// <param name="methodInfo"></param>
        private static void DeclarerCible(KeyValuePair<Type, Dictionary<MethodInfo, bool>> elementCarto, MethodInfo methodInfo)
        {
            string cible;

            if (methodInfo.IsPublic) cible = elementCarto.Key.Name;
            else cible = elementCarto.Key.Name + "_Accessor";

            var interf = elementCarto.Key.GetInterfaces().Where(x => x.FullName.Contains("Adp.Pleiade.Metier")).First();

            sW.WriteLine("                  // Initialisation des paramètres de test");
            sW.WriteLine("                  " + interf.Name + " target = CreateInternalService<" + interf.Name + ">(typeof(" + cible + "));");
            sW.WriteLine("");
        }

        /// <summary>
        /// Donne la chaîne de déclaration d'une variable, en fonction de son type
        /// </summary>
        /// <param name="unParam">le paramètre à tester</param>
        /// <returns></returns>
        private static string DonnerChaineDeDeclaration(ParameterInfo unParam, bool expectedParam)
        {
            string paramName;
            if (expectedParam) paramName = "expected"; else paramName = unParam.Name;

            if (typeof(string).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
                return "string " + paramName + " = Convert.ToString(testContextInstance.DataRow[\"" + paramName + "\"]);";

            else if (typeof(int).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
                return "int " + paramName + " = Convert.ToInt32(testContextInstance.DataRow[\"" + paramName + "\"]);";

            else if (typeof(bool).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
                return "bool " + paramName + " = Convert.ToBoolean(testContextInstance.DataRow[\"" + paramName + "\"]);";

            else if (typeof(DateTime).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
                return "DateTime " + paramName + " = Convert.ToDateTime(testContextInstance.DataRow[\"" + paramName + "\"]);";

            else if (typeof(double).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
                return "double " + paramName + " = Convert.ToDouble(testContextInstance.DataRow[\"" + paramName + "\"]);";

            else if (typeof(float).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
                return "float " + paramName + " = Convert.ToFloat(testContextInstance.DataRow[\"" + paramName + "\"]);";

            // Gestion des Record
            else if (typeof(Record).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
            {
                return "Record " + paramName + " = listRecord[Convert.ToString(testContextInstance.DataRow[\"" + paramName + "\"])];";
            }

            // Gestion des RecordList
            else if (typeof(RecordList).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
            {
                // Si le Type est RecordList
                if (typeof(RecordList).Equals(Tools.GetNormalizedType(unParam.ParameterType)))
                    return "RecordList " + paramName + " = listRecordList[Convert.ToString(testContextInstance.DataRow[\"" + paramName + "\"])];";
                // Si non, c'est un héritage ...
                else
                    return Tools.GetNormalizedType(unParam.ParameterType) + " " + paramName + " = new " + Tools.GetNormalizedType(unParam.ParameterType) + "(listRecordList[Convert.ToString(testContextInstance.DataRow[\"" + paramName + "\"])]);";
            }

            // Gestion des List<>
            else if (typeof(IList).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
            {
                var chaineDeclaration = "List<" + Tools.GetNormalizedType(unParam.ParameterType.GetGenericArguments()[0]) + "> " + paramName + " = new List<" + Tools.GetNormalizedType(unParam.ParameterType.GetGenericArguments()[0]) + ">();";
                var chaineAlimentation = "                    AlimenterListSelonCle(Convert.ToString(testContextInstance.DataRow[\"" + paramName + "\"]), " +
                    "typeof(" + Tools.GetNormalizedType(unParam.ParameterType.GetGenericArguments()[0]) + "), " + paramName + ");";
                return chaineDeclaration + '\n' + chaineAlimentation;
            }
            // Gestion des Dictionnary<>
            else if (typeof(IDictionary).IsAssignableFrom(Tools.GetNormalizedType(unParam.ParameterType)))
            {
                if (unParam.ParameterType.GetGenericArguments().Count() > 0)
                {
                    var chaineDeclaration = Tools.DonnerFormatDeclarationDictionary(unParam.ParameterType, false) + " " +
                        paramName + " = new " + Tools.DonnerFormatDeclarationDictionary(unParam.ParameterType, false) + "();";
                    var chaineAlimentation = "                    AlimenterDicoSelonCle(Convert.ToString(testContextInstance.DataRow[\"" + paramName + "\"]), " +
                        "typeof(" + Tools.GetNormalizedType(unParam.ParameterType.GetGenericArguments()[0]) + "), " +
                        "typeof(" + Tools.GetNormalizedType(unParam.ParameterType.GetGenericArguments()[1]) + "), " + paramName + ");";
                    return chaineDeclaration + '\n' + chaineAlimentation;
                }
                else
                    return Tools.GetNormalizedType(unParam.ParameterType) + " " + paramName + " = new " + Tools.GetNormalizedType(unParam.ParameterType) + "();";
            }
            // Tout autre type ...
            else
                return Tools.GetNormalizedType(unParam.ParameterType) + " " + paramName + " = new " + Tools.GetNormalizedType(unParam.ParameterType) + "();";

            //else return "// ERREUR : Type non traité : " + Tools.GetNormalizedType(unParam.ParameterType).Name;
        }

        private static void InitialiserMethodeTU(MethodInfo methode, string XMLpath, string className)
        {
            sW.WriteLine("        [DataSource(\"Microsoft.VisualStudio.TestTools.DataSource.XML\",");
            sW.WriteLine("            \"" + XMLpath + className + "TestData.xml\",");
            sW.WriteLine("            \"" + methode.Name + "TestParameters" + "\", DataAccessMethod.Sequential), TestMethod()]");
            sW.WriteLine("        public void " + methode.Name + "Test()");
            sW.WriteLine("        {");
            sW.WriteLine("              try");
            sW.WriteLine("              {");
        }

        private static void InsererHautClasseTU(KeyValuePair<Type, Dictionary<MethodInfo, bool>> elementCarto, string XMLpath)
        {
            sW.WriteLine("    [TestClass()]");
            sW.WriteLine("    public class " + elementCarto.Key.Name + "Test : AncetreTU");
            sW.WriteLine("    {");
            sW.WriteLine("        private TestContext testContextInstance;");
            sW.WriteLine("");
            sW.WriteLine("        /// <summary>");
            sW.WriteLine("        ///Obtient ou définit le contexte de test qui fournit");
            sW.WriteLine("        ///des informations sur la série de tests active ainsi que ses fonctionnalités.");
            sW.WriteLine("        /// </summary>");
            sW.WriteLine("        public TestContext TestContext");
            sW.WriteLine("        {");
            sW.WriteLine("              get { return testContextInstance; } set { testContextInstance = value; }");
            sW.WriteLine("        }");
            sW.WriteLine("");

            sW.WriteLine("        #region Attributs de tests supplémentaires");
            sW.WriteLine("        //");
            sW.WriteLine("        //Vous pouvez utiliser les attributs supplémentaires suivants lorsque vous écrivez vos tests :");
            sW.WriteLine("        //");
            sW.WriteLine("        //Utilisez ClassInitialize pour exécuter du code avant d'exécuter le premier test dans la classe");
            sW.WriteLine("        [ClassInitialize()]");
            sW.WriteLine("        public static void MyClassInitialize(TestContext testContext)");
            sW.WriteLine("        {");
            sW.WriteLine("              InitialiserObjetsComplexes(\"" +  XMLpath + elementCarto.Key.Name + "TestData.xml\");");
            sW.WriteLine("        }");
            sW.WriteLine("        #endregion");

            sW.WriteLine("");
        }

        private static void InsererEnteteTU(KeyValuePair<Type, Dictionary<MethodInfo, bool>> elementCarto)
        {
            sW.WriteLine("using System;");
            sW.WriteLine("using System.Collections.Generic;");
            sW.WriteLine("using System.Xml;");
            sW.WriteLine("using Adp.Pleiade.Fwk.Common;");
            sW.WriteLine("using Adp.Pleiade.Fwk.Service.WCF;");
            sW.WriteLine("using Adp.Pleiade.Fwk.UnitTest;");
            sW.WriteLine("using OutillageTests;");
            sW.WriteLine("using Microsoft.VisualStudio.TestTools.UnitTesting;");
            sW.WriteLine("");
            sW.WriteLine("namespace " + elementCarto.Key.Namespace + ".Tests");
            sW.WriteLine("{");
            sW.WriteLine("");
        }
    }
}
